package cate.game.pvp.pointrace;

import cate.game.framework.ModuleMan;
import cate.common.table.d.GDPointRace;
import cate.game.event.pointrace.PointRaceTimerEvent;
import cate.game.framework.timer.FIFOTimerSelector;
import cate.game.framework.timer.FIFOTimerTask;
import cate.game.framework.timer.TimerTask;
import cate.game.pvp.pointrace.timer.PointRaceTimerTask;
import cate.game.pvp.pointrace.timer.PointRaceTimerTask.Settings;

import java.util.LinkedList;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Collectors;


public class PointRaceTimerModule extends ModuleMan
        implements PointRaceTimerTask.Creator, PointRaceTimerEvent.Creator {
    private FIFOTimerSelector timerSelector;
    private final PointRaceDataManager dataManager;

    private final PointRaceTimerTask.SettingsConsumerSelector settingsConsumerSelector;

    public PointRaceTimerModule(Supplier<PointRaceDataManager> dataManagerSupplier,
                                Supplier<PointRaceTimerTask.SettingsConsumerSelector> settingsConsumerSelectorSupplier) {
        this.dataManager = Objects.requireNonNull(dataManagerSupplier.get());
        this.settingsConsumerSelector = Objects.requireNonNull(settingsConsumerSelectorSupplier.get());
    }

    @Override
    public void initDependent() throws Exception {
        this.timerSelector = new FIFOTimerSelector(buildTimerTaskList(), this::getCycleTimeMs);
        this.timerSelector.log();
    }

    /**
     * 获取循环时间
     */
    private Long getCycleTimeMs() {
        return GDPointRace.WHOLE_SEASON_MILLIS;
    }

    public void tick() {
        Optional.of(this.timerSelector)
                .map(selector -> selector.select(System.currentTimeMillis()))
                .ifPresent(TimerTask::run);
    }

    @Override
    public PointRaceTimerTask createTimerTask(Settings settings) {
        return new PointRaceTimerTask(settings,
                Optional.of(settings)
                        .map(this.settingsConsumerSelector::select)
                        .map(sc -> sc.andThen(s ->
                                this.dispatchTimerEvent(createTimerEvent(settings))))
                        .orElse(null));
    }

    private void dispatchTimerEvent(PointRaceTimerEvent timerEvent) {
        game.eventDispatcher.dispatchEvent(timerEvent);
    }

    @Override
    public PointRaceTimerEvent createTimerEvent(Settings settings) {
        PointRaceTimerEvent timerEvent = new PointRaceTimerEvent();
        timerEvent.setSeason(settings.getSeason());
        timerEvent.setId(settings.getId());
        timerEvent.setGameBody(game);
        timerEvent.setEventType(settings.getEventType());
        timerEvent.setExecutedTime(settings.getTriggerTimeMs());
        switch (timerEvent.getEventType()) {
            case OPEN:
                timerEvent.setOpenEvent(buildOpenTimerEvent(settings));
                break;
            case SWITCH:
                timerEvent.setSwitchEvent(buildSwitchTimerEvent(settings));
                timerEvent.setExecutedTime(settings.getTriggerTimeMs() - dataManager.getData().getCycleTimeMs());
                break;
            case CLOSE:
                timerEvent.setClosedEvent(buildClosedTimerEvent(settings));
                break;
            case DAY_SPAN:
                timerEvent.setDaySpanEvent(buildDaySpanEvent(settings));
                break;
            case RANK_DAY_RWD:
                timerEvent.setRankDayRwdEvent(buildRankDayRwdEvent(settings));
                break;
            case RANK_SEASON_RWD:
                timerEvent.setRankSeasonRwdEvent(buildRankSeasonRwdEvent(settings));
                break;
        }
        return timerEvent;
    }

    private PointRaceTimerEvent.DaySpanEvent buildDaySpanEvent(Settings settings) {
        PointRaceTimerEvent.DaySpanEvent daySpanEvent = new PointRaceTimerEvent.DaySpanEvent();
        daySpanEvent.setFromDay(settings.getDay() - 1);
        daySpanEvent.setToDay(settings.getDay());
        return daySpanEvent;
    }

    private PointRaceTimerEvent.RankSeasonRwdEvent buildRankSeasonRwdEvent(Settings settings) {
        PointRaceTimerEvent.RankSeasonRwdEvent rankSeasonRwdEvent = new PointRaceTimerEvent.RankSeasonRwdEvent();
        rankSeasonRwdEvent.setDay(settings.getDay());
        return rankSeasonRwdEvent;
    }

    private PointRaceTimerEvent.ClosedEvent buildClosedTimerEvent(Settings settings) {
        PointRaceTimerEvent.ClosedEvent closedEvent = new PointRaceTimerEvent.ClosedEvent();
        closedEvent.setClosedTime(settings.getTriggerTimeMs());
        return closedEvent;
    }

    private PointRaceTimerEvent.SwitchEvent buildSwitchTimerEvent(Settings settings) {
        PointRaceTimerEvent.SwitchEvent switchEvent = new PointRaceTimerEvent.SwitchEvent();
        switchEvent.setSwitchTime(settings.getTriggerTimeMs());
        switchEvent.setFromSeason(settings.getSeason() - 1);
        switchEvent.setToSeason(settings.getSeason());
        return switchEvent;
    }

    private PointRaceTimerEvent.RankDayRwdEvent buildRankDayRwdEvent(Settings settings) {
        PointRaceTimerEvent.RankDayRwdEvent rankDayRwdEvent = new PointRaceTimerEvent.RankDayRwdEvent();
        rankDayRwdEvent.setDay(settings.getDay());
        return rankDayRwdEvent;
    }

    private PointRaceTimerEvent.OpenEvent buildOpenTimerEvent(Settings settings) {
        PointRaceTimerEvent.OpenEvent openEvent = new PointRaceTimerEvent.OpenEvent();
        openEvent.setOpenTime(dataManager.getData().getOpenTime());
        openEvent.setSession(dataManager.getData().getSeason());
        openEvent.setClosedTime(dataManager.getData().getClosedTime());
        return openEvent;
    }

    protected LinkedList<FIFOTimerTask> buildTimerTaskList() {
        AtomicInteger counter = new AtomicInteger(0);
        return this.dataManager.getData().getTimerSettings()
                .stream()
                .peek(settings -> settings.setId(counter.incrementAndGet()))
                .map(this::createTimerTask)
                .filter(Objects::nonNull)
                .collect(Collectors.toCollection(LinkedList::new));
    }
}
